package org.andromda.android.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

public class Utils {

	private static final double ONE_KILOBYTE = 1024;

	private static final SimpleDateFormat DATE_PARSER = new SimpleDateFormat(
			"yyyy-MM-d'T'HH:mm:ss.SSSZ");;

	/**
	 * Converts an xmlDate '2002-05-30T09:30:10.5' into a java object.
	 * 
	 * @param xmlDate
	 * @return
	 * @throws java.text.ParseException
	 */
	public static Date toJavaDate(final String xmlDate)
			throws java.text.ParseException {
		Date date;
		if (!TextUtils.isEmpty(xmlDate)) {
			date = DATE_PARSER.parse(xmlDate);
		} else {
			date = null;
		}
		return date;
	}

	public static Uri concatUri(final Uri base, final String... pathsegments) {
		Uri result = base;
		for (final String pathSegment : pathsegments) {
			result = Uri.withAppendedPath(result, pathSegment);
		}
		return result;
	}

	public static Object deserialize(final byte objectData[]) {
		if (objectData == null) {
			throw new IllegalArgumentException("The byte[] must not be null");
		} else {
			final ByteArrayInputStream bais = new ByteArrayInputStream(
					objectData);
			return deserialize(((bais)));
		}
	}

	public static Object deserialize(final InputStream inputStream) {
		if (inputStream == null) {
			throw new IllegalArgumentException(
					"The InputStream must not be null");
		}
		ObjectInputStream in = null;
		try {
			in = new ObjectInputStream(inputStream);
			final Object obj = in.readObject();
			return obj;
		} catch (final ClassNotFoundException ex) {
			throw new RuntimeException(ex);
		} catch (final IOException ex) {
			throw new RuntimeException(ex);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (final IOException ex) {
			}
		}
	}

	/**
	 * Determines the application storage path. For now
	 * /sdcard/com.zia.freshdocs.
	 * 
	 * @return
	 */
	public static StringBuilder getAppStoragePath() {
		final StringBuilder targetPath = new StringBuilder();

		final File sdCard = Environment.getExternalStorageDirectory();

		if (sdCard != null) {
			targetPath.append(sdCard.getAbsolutePath()).append(File.separator);
			final String packageName = Utils.class.getPackage().getName();
			targetPath.append(packageName);
		}

		return targetPath;
	}

	/**
	 * Returns a File handle based on the appropriate storage path. The
	 * application storage path is created if it does not already exist.
	 * 
	 * @param description
	 * @param filesize
	 * @return
	 */
	public static File getFile(final String name, final long filesize) {
		final File sdCard = Environment.getExternalStorageDirectory();
		final StringBuilder targetPath = Utils.getAppStoragePath();

		if (sdCard.canWrite() && targetPath.length() > 0) {
			final File appStorage = new File(targetPath.toString());

			if (!appStorage.exists()) {
				if (!appStorage.mkdirs()) {
					return null;
				}
			}

			targetPath.append(File.separator).append(name);
			final File target = new File(targetPath.toString());

			try {
				if (target.exists()) {
					if (target.length() != filesize && filesize != -1) {
						target.delete();
					} else {
						return target;
					}
				}

				if (target.createNewFile()) {
					// target.deleteOnExit();
					return target;
				}
			} catch (final IOException e) {
				Log.e("", "Error in getFileStream", e);
			}
		}

		return null;
	}

	public static String getSizeString(final long bytes) {
		final String[] units = new String[] { "Byte", "KB", "MB", "GB", "TB",
				"PB" };

		// log1024(bytesSec)
		final double idx = Math.log(bytes) / Math.log(ONE_KILOBYTE);
		final int intIdx = Math.max(0,
				Math.min((int) Math.floor(idx), units.length - 1));
		final double reducedRate = bytes / Math.pow(ONE_KILOBYTE, intIdx);
		final DecimalFormat df = new DecimalFormat();
		final int ndigit = (int) Math
				.floor(Math.max(Math.log10(reducedRate), 0));
		df.setMinimumFractionDigits(0);
		df.setGroupingUsed(false);
		df.setMaximumFractionDigits(Math.max(0, 2 - ndigit));
		return df.format(reducedRate) + " " + units[intIdx];
	}

	/**
	 * Executes the network requests on a separate thread.
	 * 
	 * @param runnable
	 *            The runnable instance containing network mOperations to be
	 *            executed.
	 */
	public static Thread performOnBackgroundThread(final Runnable runnable) {
		final Thread t = new Thread() {
			@Override
			public void run() {
				runnable.run();
			}
		};
		t.start();
		return t;
	}

	/**
	 * Removes the elements in <code>remove</code> from <code>collection</code>.
	 * That is, this method returns a list containing all the elements in
	 * <code>c</code> that are not in <code>remove</code>. The cardinality of an
	 * element <code>e</code> in the returned collection is the same as the
	 * cardinality of <code>e</code> in <code>collection</code> unless
	 * <code>remove</code> contains <code>e</code>, in which case the
	 * cardinality is zero. This method is useful if you do not wish to modify
	 * <code>collection</code> and thus cannot call
	 * <code>collection.removeAll(remove);</code>.
	 * 
	 * @param collection
	 *            the collection from which items are removed (in the returned
	 *            collection)
	 * @param remove
	 *            the items to be removed from the returned
	 *            <code>collection</code>
	 * @return a <code>List</code> containing all the elements of <code>c</code>
	 *         except any elements that also occur in <code>remove</code>.
	 * @throws NullPointerException
	 *             if either parameter is null
	 */
	public static <T> List<T> removeAll(final Collection<T> collection,
			final Collection<T> remove) {
		final List<T> list = new ArrayList<T>();
		for (final T obj : collection) {
			if (remove.contains(obj) == false) {
				list.add(obj);
			}
		}
		return list;
	}

	// -----------------------------------------------------------------------
	/**
	 * Returns a List containing all the elements in <code>collection</code>
	 * that are also in <code>retain</code>. The cardinality of an element
	 * <code>e</code> in the returned list is the same as the cardinality of
	 * <code>e</code> in <code>collection</code> unless <code>retain</code> does
	 * not contain <code>e</code>, in which case the cardinality is zero. This
	 * method is useful if you do not wish to modify the collection
	 * <code>c</code> and thus cannot call
	 * <code>collection.retainAll(retain);</code>.
	 * 
	 * @param collection
	 *            the collection whose contents are the target of the #retailAll
	 *            operation
	 * @param retain
	 *            the collection containing the elements to be retained in the
	 *            returned collection
	 * @return a <code>List</code> containing all the elements of <code>c</code>
	 *         that occur at least once in <code>retain</code>.
	 * @throws NullPointerException
	 *             if either parameter is null
	 * @since Commons Collections 3.2
	 */
	public static <T> List<T> retainAll(final Collection<T> collection,
			final Collection<T> retain) {
		final List list = new ArrayList(Math.min(collection.size(),
				retain.size()));

		for (final Object obj : collection) {
			if (retain.contains(obj)) {
				list.add(obj);
			}
		}
		return list;
	}

	public static byte[] serialize(final Serializable obj) {
		final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
		serialize(obj, ((baos)));
		return baos.toByteArray();
	}

	public static void serialize(final Serializable obj,
			final OutputStream outputStream) {
		if (outputStream == null) {
			throw new IllegalArgumentException(
					"The OutputStream must not be null");
		}
		ObjectOutputStream out = null;
		try {
			out = new ObjectOutputStream(outputStream);
			out.writeObject(obj);
		} catch (final IOException ex) {
			throw new RuntimeException(ex);
		} finally {
			try {
				if (out != null) {
					out.close();
				}
			} catch (final IOException ex) {
			}
		}
	}

}
